[Amazon SageMaker] オブジェクト検出のモデルをNeoで最適化して、Jetson Nano+OpenCV+Webカメラで使用してみました
1 はじめに
CX事業本部の平内(SIN)です。
Amazon SageMager Neo(以下、Neo)を使用すると、既存のモデルを各種アーキテクチャのエッジデバイスで動作させる事ができます。 今回は、Amazon SageMager(以下、SageMaker)の組み込みアルゴリズムである「物体検出」で作成したモデルをNeoで最適化し、Jetson Nanoで利用してみました。
最初に動作している様子です。
Jetson Nanoに接続したWebカメラの画像を推論にかけ、検出結果を表示しています。推論は、約0.25秒で処理され、4FPS程度のフレームレートとなります。
2 データセット
データセットのアノテーションは、Microsoft VoTT Ver2.2.0 を利用させて頂きました。
アヒルとトマト羊の写真を20枚ずつ撮影し、バウンディングボックスの付与作業を行いました。
データとしては、40件です。
VoTTで作成された、データセットは、一旦、GroundTruth形式に変換し、最終的にRecordIO形式としています。
参考:[Amazon Rekognition] VoTTで作成したデータをCustom Labelsで利用可能なAmazon SageMaker Ground Truth形式に変換してみました
参考:[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをオブジェクト検出で利用可能なRecordIO形式に変換してみました
変換が完了したデータは、学習用32件、検証用8件となっています。
全データ: 40件 [0]TOMATO: 20件 [1]AHIRU: 20件 TOMATO => 16:4 残り:20件 AHIRU => 16:4 残り:0件 Train: 32件
AHIRU_20-TOMATO_20_S3 ├── train.idx ├── train.lst ├── train.rec ├── validation.idx ├── validation.lst └── validation.rec
train.rec及び、validation.recは、S3にアップロードしました。
3 学習
組み込みアルゴリズムである「物体検出」のトレーニングは、ml.p3.2xlarge(50G) で125秒でした。
指定したパラメータは、以下のとおりです。殆ど、デフォルト値のままですが、以下が変更されています。
- base_network: resnet-50 (VGG-16では、Neoでのコンパイルに失敗しました)
- mini_batch_size: 4 (検証データが、各4件しか無いため)
- num_training_samples: 32(学習データは、32件となっているため)
base_network resnet-50 early_stopping false early_stopping_min_epochs 10 early_stopping_patience 5 early_stopping_tolerance 0.0 epochs 50 freeze_layer_pattern false image_shape 512 label_width 350 learning_rate 0.001 lr_scheduler_factor 0.1 mini_batch_size 4 momentum 0.9 nms_threshold 0.45 num_classes 2 num_training_samples 32 optimizer sgd overlap_threshold 0.5 use_pretrained_model 1 weight_decay 0.0005
学習の経過です。epochが40を超えたあたりから、mAPは、1.0となっています。
epoch mAP smooth_l1 cross_entropy ----------------------------------------- 0 0.005 0.707 1.469 5 0.414 0.593 1.116 10 0.154 0.599 1.072 15 0.385 0.591 1.041 20 0.583 0.583 0.993 25 0.646 0.541 0.927 30 0.792 0.552 0.863 35 0.596 0.522 0.795 40 1.0 0.421 0.715 45 1.0 0.429 0.692 50 1.0 0.41 0.668
出来上がったモデルは、97.5Mbyteでした。
4 MXNet用への変換
「物体検出」で作成したモデルを、Neoで使用するためには、変換(損失層の削除とNMS層を追加)が必要になります。
変換は、SageMakerのノートブックインスタンス(JupyterNotebook)上で、https://github.com/zhreshold/mxnet-ssdのdeploy.pyを使用して行いました。
参考:[Amazon SageMaker] 組み込みアルゴリズムのオブジェクト検出(ResNet-50)をMac上のMXNetで利用してみました
変換で出力されるdeploy_model_algo_1-0000.params及び、deploy_model_algo_1-symbol.jsonをtar.gz で固めています。
% ls deploy_model_algo_1-0000.params deploy_model_algo_1-symbol.json % tar cvfz deploy_model.tar.gz * a deploy_model_algo_1-0000.params a deploy_model_algo_1-symbol.json % ls *.tar.gz deploy_model.tar.gz
5 Neo
下記の諸元で、上記のdeploy_model.tar.gzを最適化しています。
- データ入力値: {"data":[1,3,512,512]}
- 機械学習フレームワーク: MXNet
- 対象デバイス: jetson_nano
最適化は、すぐに終わります。
出力された、deploy_model-jetson_nano.tar.gzの中は、下記の4つのファイルになっています。
6 Jetson Nano
Neoで最適化されたモデルを使用するためには、DLRのインストールと、スワップの追加(モデルのサイズが大きい場合)が必要です。
(1) DLRのインストール
DLRのインストールは、下記のドキュメント通り、cmake-3.17.2をインストールしたのち、DLRをcloneしてコンパイルしました。
https://neo-ai-dlr.readthedocs.io/en/latest/install.html
インストール完了を確認している様子です。
$ python3 Python 3.6.9 (default, Apr 18 2020, 01:56:04) [GCC 8.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import dlr >>> dlr.__version__ '1.2.0' >>>
(2) SWAP
今回作成したモデルは、デフォルト(メモリ4G、スワップ2G)のままでは、動作できなかった為、スワップを増やしています。
作業した手順は、以下のとおりです。 デフォルトのSWAPを一旦解除し、6Gの /swapfile を作成して、アクティベートし直しました。
$ grep SwapTotal /proc/meminfo SwapTotal: 2029696 kB $sudo swapoff -a //swapプロセスを解除 $sudo dd if=/dev/zero of=/swapfile bs=1G count=6 //領域確保 6+0 records in 6+0 records out 6442450944 bytes (6.4 GB, 6.0 GiB) copied, 331.429 s, 19.4 MB/s $sudo mkswap /swapfile // SWAPファイル作成 mkswap: /swapfile: insecure permissions 0644, 0600 suggested. Setting up swapspace version 1, size = 6 GiB (6442446848 bytes) no label, UUID=89e03939-ce9a-483d-a417-b9e7ef073ed5 $sudo swapon /swapfile //アクティベート swapon: /swapfile: insecure permissions 0644, 0600 suggested. $grep SwapTotal /proc/meminfo SwapTotal: 6291452 kB
7 実行
Neoで作成したモデルを./modelに展開しています。
$ mkdir model $ tar xvfz deploy_model-jetson_nano.tar.gz -C model $ ls -la model total 144084 drwxrwxr-x 2 nvidia nvidia 4096 6月 25 12:48 . drwxrwxr-x 3 nvidia nvidia 4096 6月 25 12:47 .. -rw-r--r-- 1 nvidia nvidia 154 6月 25 10:17 compiled.meta -rw-r--r-- 1 nvidia nvidia 5917 6月 25 10:17 compiled_model.json -rw-r--r-- 1 nvidia nvidia 32 6月 25 10:17 compiled.params -rwxr-xr-x 1 nvidia nvidia 147516536 6月 25 10:17 compiled.so
実行しているコードは、以下のとおりです。
import cv2 import numpy as np import dlr import time MODEL_PATH = './model' CLASSES = ['TOMATO','AHIRU'] COLORS = [(128, 0, 0),(0, 128, 0)] SHAPE = 512 DEVICE_ID = 0 WIDTH = 800 HEIGHT = 600 GST_STR = ('v4l2src device=/dev/video{} ! video/x-raw, width=(int){}, height=(int){} ! videoconvert ! appsink').format(DEVICE_ID, WIDTH, HEIGHT) def main(): print("Ver dlr:{} cv2:{}".format(dlr.__version__, cv2.__version__)) # Video Initialize cap = cv2.VideoCapture(GST_STR, cv2.CAP_GSTREAMER) width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) fps = cap.get(cv2.CAP_PROP_FPS) print("fps:{} width:{} height:{}".format(fps, width, height)) # Model Initialize print("DLRModel() start") model = dlr.DLRModel(MODEL_PATH, 'gpu') print("DLRModel() end") print("model.get_input_dtypes(): {}".format(model.get_input_dtypes())) print("model.get_input_names(): {}".format(model.get_input_names())) print("model OK") while(True): _, frame = cap.read() if(frame is None): continue frame = frame[0 : int(height), 0 : int(height)] # 横長の長方形 => 正方形 frame = cv2.resize(frame, dsize=(SHAPE, SHAPE)) img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = img.transpose((2, 0, 1)) img = img[np.newaxis, :] print("img.shape: {}".format(img.shape)) start = time.time() out = model.run({'data' : img}) elapsed_time = time.time() - start print("{} [Sec]".format(elapsed_time)) for det in out[0][0]: if(det[0]%1 == 0 and det[1] > 0): index = int(det[0]) confidence = det[1] x1 = int(det[2] * SHAPE) y1 = int(det[3] * SHAPE) x2 = int(det[4] * SHAPE) y2 = int(det[5] * SHAPE) print("[{}] {:.1f} {}, {}, {}, {}".format(CLASSES[index], confidence, x1, y1, x2, y2)) if(confidence > 0.5): # 信頼度 frame = cv2.rectangle(frame,(x1, y1), (x2, y2), COLORS[index],2) frame = cv2.rectangle(frame,(x1, y1), (x1 + 150,y1-20), COLORS[index], -1) label = "{} {:.2f}".format(CLASSES[index], confidence) frame = cv2.putText(frame,label,(x1+2, y1-2), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1, cv2.LINE_AA) # 画像表示 cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break main()
8 最後に
今回は、SageMakerの組み込みアルゴリズムである「物体検出」で作成したモデルをNeoで最適化し、Jetson Nanoで利用してみました。
手順でも示していますが、「物体検出」で作成されたモデルは、そのままでは、Neoで利用できないので、注意が必要だと思いました。